#include "glxvideooutput.h"

#include <string>
#include <iostream>
#include <assert.h>
#include <X11/extensions/Xrender.h>
#include <X11/Xatom.h>
#include <X11/XKBlib.h>


#include "event.h"
#include "Common/stringoperations.h"

using namespace std;

typedef struct {
    unsigned long flags;
    unsigned long functions;
    unsigned long decorations;
    long input_mode;
    unsigned long status;
} MotifWmHints, MwmHints;

#define MWM_HINTS_FUNCTIONS     (1L << 0)
#define MWM_HINTS_DECORATIONS   (1L << 1)

#define NET_WM_STATE_TOGGLE 2

static Atom XA_NET_WM_STATE;
static Atom XA_NET_WM_STATE_ABOVE;
static Atom XA_WIN_LAYER;

GlxVideoOutput::GlxVideoOutput()
: m_HasClosed(false)
{
}

GlxVideoOutput::~GlxVideoOutput()
{
    XUnmapWindow (m_pDisplay, m_Window);
    XDestroyWindow (m_pDisplay, m_Window);
    XCloseDisplay (m_pDisplay);
}

void GlxVideoOutput::initialize(int width, int height, bool fullScreen)
{
    m_Width = width;
    m_Height = height;

    m_pDisplay = XOpenDisplay(0);
    assert(m_pDisplay);

    XA_NET_WM_STATE = XInternAtom(m_pDisplay, "_NET_WM_STATE", False);
    XA_NET_WM_STATE_ABOVE = XInternAtom(m_pDisplay, "_NET_WM_ABOVE", False);
    XA_WIN_LAYER = XInternAtom(m_pDisplay, "_WIN_LAYER", False);

    int renderEventBase;
    int renderErrorBase;

    if (!XRenderQueryExtension(m_pDisplay, &renderEventBase, &renderErrorBase))
    {
        cerr << "No RENDER extension found!" << endl;
        return;
    }

    GLXFBConfig*            pFbConfig = NULL;
    GLXFBConfig             renderFBConfig;
    XVisualInfo*            pVisualInfo = NULL;
    XSetWindowAttributes    winAttributes;
    int                     numElements;

    int doubleBufferAttributes[] = {
        GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
        GLX_RENDER_TYPE,   GLX_RGBA_BIT,
        GLX_SAMPLE_BUFFERS_ARB, 1,
        GLX_SAMPLES_ARB, 4,
        GLX_DOUBLEBUFFER,  True,
        GLX_RED_SIZE,      1,
        GLX_GREEN_SIZE,    1,
        GLX_BLUE_SIZE,     1,
        GLX_ALPHA_SIZE,    1,
        None
    };

    pFbConfig = glXChooseFBConfig(m_pDisplay, DefaultScreen(m_pDisplay), doubleBufferAttributes, &numElements);
    assert(pFbConfig);

    pVisualInfo = glXGetVisualFromFBConfig(m_pDisplay, pFbConfig[0]);
    renderFBConfig = pFbConfig[0];

    winAttributes.colormap = XCreateColormap(m_pDisplay, RootWindow(m_pDisplay, pVisualInfo->screen),pVisualInfo->visual, AllocNone);

    winAttributes.border_pixel = 0;
    winAttributes.event_mask = StructureNotifyMask | ButtonPressMask | KeyPressMask;
    winAttributes.override_redirect = true;

    m_Window = XCreateWindow(m_pDisplay, RootWindow(m_pDisplay, pVisualInfo->screen), 0, 0, width, height, 0, pVisualInfo->depth, InputOutput,
                             pVisualInfo->visual, CWBorderPixel | CWColormap | CWEventMask, &winAttributes);
    m_GlxContext = glXCreateNewContext(m_pDisplay, renderFBConfig, GLX_RGBA_TYPE, 0, GL_TRUE);
    m_GlxWindow = glXCreateWindow(m_pDisplay, renderFBConfig, m_Window, 0);
    Atom wmDelete = XInternAtom(m_pDisplay, "WM_DELETE_WINDOW", True);
    XSetWMProtocols(m_pDisplay, m_Window, &wmDelete, 1);
    XMapWindow (m_pDisplay, m_Window);
    glXMakeContextCurrent(m_pDisplay, m_GlxWindow, m_GlxWindow, m_GlxContext);

    setFullScreen(fullScreen);
    toggleVSync();

    //initialize drag and drop
    m_DragNDrop.initialize(m_pDisplay, m_Window);
    XFree (pVisualInfo);
}

void GlxVideoOutput::setWindowTitle(const string& title)
{
    XTextProperty textProp;
    textProp.value = (unsigned char *) title.c_str();
    textProp.encoding = XA_STRING;
    textProp.format = 8;
    textProp.nitems = title.length();

    XSetWMName(m_pDisplay, m_Window, &textProp);
}

void GlxVideoOutput::setWindowDecorations(bool enabled)
{
    unsigned char* pucData;
    int iFormat;
    unsigned long ulItems;
    unsigned long ulBytesAfter;
    Atom typeAtom;
    MotifWmHints newHints;

    Atom hintsAtom = XInternAtom (m_pDisplay, "_MOTIF_WM_HINTS", True);

    XGetWindowProperty (m_pDisplay, m_Window, hintsAtom, 0,
                        sizeof (MotifWmHints) / sizeof (long),
                        False, AnyPropertyType, &typeAtom,
                        &iFormat, &ulItems, &ulBytesAfter, &pucData);

    newHints.flags = MWM_HINTS_DECORATIONS;
    newHints.decorations = enabled ? 1:0;

    XChangeProperty (m_pDisplay, m_Window, hintsAtom, hintsAtom,
                    32, PropModeReplace, (unsigned char *) &newHints,
                    sizeof (MotifWmHints) / sizeof (long));
}

void GlxVideoOutput::setFullScreen(bool enabled)
{
    m_FullScreen = enabled;
    setWindowDecorations(!m_FullScreen);

    XWindowChanges changes;
    //XSetWindowAttributes winAttrib;
    unsigned int valueMask = CWX | CWY | CWWidth | CWHeight;

    if (m_FullScreen)
    {
        changes.x = 0;
        changes.y = 0;
        changes.width = DisplayWidth(m_pDisplay,DefaultScreen(m_pDisplay));
        changes.height = DisplayHeight(m_pDisplay,DefaultScreen(m_pDisplay));
        changes.stack_mode = Above;
        valueMask |= CWStackMode;

    }
    else
    {
        changes.x = m_XPosition;
        changes.y = m_YPosition;
        changes.width = m_Width;
        changes.height = m_Height;
    }

    //winAttrib.override_redirect = True;
    //XChangeWindowAttributes(m_pDisplay, m_Window, CWOverrideRedirect, &winAttrib);
    XMapRaised(m_pDisplay, m_Window);
    //XRaiseWindow(m_pDisplay, m_Window);
    XConfigureWindow(m_pDisplay, m_Window, valueMask, &changes);
    alwaysOnTop();
    //XFlush(m_pDisplay);
}

void GlxVideoOutput::alwaysOnTop()
{
    //~ XClientMessageEvent xev;
    //~ memset(&xev, 0, sizeof(xev));

    //~ xev.type = ClientMessage;
    //~ xev.message_type = XA_NET_WM_STATE;
    //~ xev.display = m_pDisplay;
    //~ xev.window = m_Window;
    //~ xev.format = 32;
    //~ xev.data.l[0] = 10;

    //if (vo_fs_type & vo_wm_STAYS_ON_TOP)
        //xev.data.l[1] = XA_NET_WM_STATE_STAYS_ON_TOP;
    //else if (vo_fs_type & vo_wm_ABOVE)
        //xev.data.l[1] = XA_NET_WM_STATE_ABOVE;
    //else if (vo_fs_type & vo_wm_FULLSCREEN)
    //    xev.data.l[1] = XA_NET_WM_STATE_FULLSCREEN;
    //else if (vo_fs_type & vo_wm_BELOW)
        // This is not fallback. We can safely assume that situation where
        // only NETWM_STATE_BELOW is supported and others not, doesn't exist.
    //    xev.data.l[1] = XA_NET_WM_STATE_BELOW;

    //XSendEvent(m_pDisplay, m_Window, False, SubstructureRedirectMask, reinterpret_cast<XEvent*>(&xev));
    //state = XGetAtomName(mDisplay, xev.data.l[1]);
    //mp_msg(MSGT_VO, MSGL_V,
    //       "[x11] NET style stay on top (layer %d). Using state %s.\n",
    //       layer, state);
    //XFree(state);

    long propvalue = 12;
    XChangeProperty(m_pDisplay, m_Window, XA_WIN_LAYER, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&propvalue, 1);
}

void GlxVideoOutput::toggleVSync()
{
    m_VSync = !m_VSync;

    if (GLEE_GLX_SGI_swap_control)
    {
        if (m_VSync)
        {
            glXSwapIntervalSGI(1);
        }
        else
        {
            glXSwapIntervalSGI(2);
        }
    }
}

void GlxVideoOutput::swapBuffers()
{
    glXSwapBuffers(m_pDisplay, m_GlxWindow);
}

bool GlxVideoOutput::checkResizeEvent(ResizeEvent& event)
{
    XEvent xEvent;
    bool gotEvent = false;

    //remove old resize events to prevent delay
    while(XCheckTypedEvent(m_pDisplay, ConfigureNotify, &xEvent))
    {
        gotEvent = true;
    }

    if (gotEvent)
    {
        if (!m_FullScreen)
        {
            m_Width = xEvent.xconfigure.width;
            m_Height = xEvent.xconfigure.height;
            m_XPosition = xEvent.xconfigure.x;
            m_YPosition = xEvent.xconfigure.y;
        }

        event.width = xEvent.xconfigure.width;
        event.height = xEvent.xconfigure.height;
    }

    return gotEvent;
}

void GlxVideoOutput::checkCientMessages()
{
    XEvent xEvent;
    bool gotEvent = XCheckTypedEvent(m_pDisplay, ClientMessage, &xEvent);

    if (gotEvent)
    {
        cout << "event" << endl;
        if(xEvent.xclient.message_type == m_DragNDrop.XdndDrop)
        {
            cout << "droppy drop" << endl;
            m_DragNDrop.handleDropEvent();
        }
        else if (xEvent.xclient.message_type == m_DragNDrop.XdndPosition)
        {
            m_DragNDrop.handlePositionEvent(xEvent);
        }
        else if (xEvent.xclient.message_type == m_DragNDrop.XdndEnter)
        {
            cout << "enter window" << endl;
            m_DragNDrop.handleEnterEvent(xEvent);
        }
        else if (xEvent.xclient.message_type == m_DragNDrop.XdndLeave)
        {
            cout << "leave window" << endl;
            m_DragNDrop.handleLeaveEvent(xEvent);
        }
        else if (*XGetAtomName(m_pDisplay, xEvent.xclient.message_type) == *"WM_PROTOCOLS")
        {
            m_HasClosed = true;
        }
    }
}

bool GlxVideoOutput::hasClosed()
{
    checkCientMessages();
    return m_HasClosed;
}

bool GlxVideoOutput::checkFiledropEvent(FiledropEvent& event)
{
    XEvent xEvent;
    bool gotEvent = XCheckTypedEvent(m_pDisplay, SelectionNotify, &xEvent);

    if (gotEvent)
    {
        if(xEvent.xselection.property == m_DragNDrop.XdndActionPrivate)
        {
            string filenames = m_DragNDrop.getDroppedFilenames(xEvent);
            StringOperations::replace(filenames, "%20", " ");
            StringOperations::replace(filenames, "file://", "");
            StringOperations::tokenize(filenames, "\r\n", event.filenames);
        }
    }

    return gotEvent;
}

bool GlxVideoOutput::checkKeyEvent(KeyEvent& event)
{
    XEvent xEvent;
    bool gotEvent = false;

    gotEvent = XCheckWindowEvent(m_pDisplay, m_Window, KeyPressMask | KeyReleaseMask, &xEvent);

    if (gotEvent)
    {
        if (xEvent.type == KeyPress)
        {
            event.pressed = true;
        }
        else if (xEvent.type == KeyRelease)
        {
            event.pressed = false;
        }

        KeySym keySymbol = XkbKeycodeToKeysym(m_pDisplay, xEvent.xkey.keycode, 0, 0);
        switch (keySymbol)
        {
            case XK_a:
                event.keycode = KeyEvent::KEY_a;
                break;
            case XK_d:
                event.keycode = KeyEvent::KEY_d;
                break;
            case XK_f:
                event.keycode = KeyEvent::KEY_f;
                break;
            case XK_h:
                event.keycode = KeyEvent::KEY_h;
                break;
            case XK_q:
                event.keycode = KeyEvent::KEY_q;
                break;
            case XK_r:
                event.keycode = KeyEvent::KEY_r;
                break;
            case XK_v:
                event.keycode = KeyEvent::KEY_v;
                break;
            case XK_x:
                event.keycode = KeyEvent::KEY_x;
                break;
            case XK_y:
                event.keycode = KeyEvent::KEY_y;
                break;
            case XK_space:
                event.keycode = KeyEvent::KEY_SPACE;
                break;
            case XK_Left:
                event.keycode = KeyEvent::KEY_LEFT;
                break;
            case XK_Right:
                event.keycode = KeyEvent::KEY_RIGHT;
                break;
            case XK_Up:
                event.keycode = KeyEvent::KEY_UP;
                break;
            case XK_Down:
                event.keycode = KeyEvent::KEY_DOWN;
                break;
            case XK_F1:
                event.keycode = KeyEvent::KEY_F1;
                break;
            case XK_F2:
                event.keycode = KeyEvent::KEY_F2;
                break;
            case XK_F3:
                event.keycode = KeyEvent::KEY_F3;
                break;
            case XK_F4:
                event.keycode = KeyEvent::KEY_F4;
                break;
            case XK_F5:
                event.keycode = KeyEvent::KEY_F5;
                break;
            case XK_F6:
                event.keycode = KeyEvent::KEY_F6;
                break;
            case XK_F7:
                event.keycode = KeyEvent::KEY_F7;
                break;
            case XK_F8:
                event.keycode = KeyEvent::KEY_F8;
                break;
            case XK_F9:
                event.keycode = KeyEvent::KEY_F9;
                break;
            case XK_F10:
                event.keycode = KeyEvent::KEY_F10;
                break;
            case XK_F11:
                event.keycode = KeyEvent::KEY_F11;
                break;
            case XK_F12:
                event.keycode = KeyEvent::KEY_F12;
                break;
            default:
                event.keycode = KeyEvent::KEY_NONE;
        }
    }

    return gotEvent;
}
